using System;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Security.Permissions;
#if !PocketPC || DesignTime
using System.Runtime.Serialization;
using System.ComponentModel;
#endif
using System.Reflection;

namespace GeoFramework
{
	/// <summary>
	/// Represents a line connected by two points on Earth's surface.
	/// </summary>
#if !PocketPC || DesignTime
#if Framework20
    [Obfuscation(Feature = "renaming", Exclude = false, ApplyToMembers = true)]
    [Obfuscation(Feature = "controlflow", Exclude = true, ApplyToMembers = true)]
    [Obfuscation(Feature = "stringencryption", Exclude = false, ApplyToMembers = true)]
#endif
	[Serializable()]
    [ImmutableObject(true)]
#endif
#if Framework20
    [XmlSchemaProvider("GetXmlSchema")]
#endif
    public sealed class Segment : IFormattable
#if !PocketPC || Framework20
		, IXmlSerializable
#endif
#if !PocketPC || DesignTime
, ISerializable
#endif
	{
        
        private Position pStart;
		private Position pEnd;

        /// <summary>
        /// Creates a new instance with empty values.
        /// </summary>
        public Segment()
        {
            pStart = Position.Empty;
            pEnd = Position.Empty;
        }

		/// <summary>Creates a new instance using the specified end points.</summary>
		public Segment(Position start, Position end)
		{
			pStart = start;
			pEnd = end;
		}

        #if !PocketPC || DesignTime
        /// <summary>
        /// Creates a new instance by de-serializing previously-serialized information.
        /// </summary>
        /// <param name="info">A <strong>SerializationInfo</strong> object containing serialized data.</param>
        /// <param name="context">A <strong>StreamingContext</strong> object describing the context of serialization.</param>
        /// <remarks>This constructor will de-serialize a previously-serialized object.</remarks>
        private Segment(SerializationInfo info, StreamingContext context)
        {
            pStart = (Position)info.GetValue("Start", typeof(Position));
            pEnd = (Position)info.GetValue("End", typeof(Position));
        }
#endif

		/// <summary>
		/// Returns the distance from the starting point to the end point.
		/// </summary>
		//[CLSCompliant(false)]
		public Distance Distance
		{
			get
			{
				return pStart.DistanceTo(pEnd);
			}
		}

		/// <summary>
		/// Returns the bearing from the start to the end of the line.
		/// </summary>
		public Azimuth Bearing
		{
			get
			{
				return pStart.BearingTo(pEnd);
			}
		}

		/// <summary>
		/// Returns the starting point of the segment.
		/// </summary>
		public Position Start
		{
			get
			{
				return pStart;
			}
		}

		/// <summary>
		/// Returns the end point of the segment.
		/// </summary>
		public Position End
		{
			get
			{
				return pEnd;
			}
		}

		/// <summary>Returns the location halfway from the start to the end point.</summary>
		public Position Center
		{
			get
			{
				return new Position(pStart.Latitude.Add(pEnd.Latitude.DecimalDegrees).Multiply(0.5),
                                    pStart.Longitude.Add(pEnd.Longitude.DecimalDegrees).Multiply(0.5));
			}
		}

		/// <summary>
		/// Returns the distance from the segment to the specified position.
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
		/// <remarks>This method analyzes the relative position of the segment to the line to determine the
		/// best mathematical approach.</remarks>
		//[CLSCompliant(false)]
		public Distance DistanceTo(Position position)
		{
			if(pStart.Equals(pEnd))
				return position.DistanceTo(pStart);
			Position Delta = pEnd.Subtract(pStart);
			double Ratio = ((position.Longitude.DecimalDegrees - pStart.Longitude.DecimalDegrees) 
                * Delta.Longitude.DecimalDegrees + (position.Latitude.DecimalDegrees - pStart.Latitude.DecimalDegrees) 
                * Delta.Latitude.DecimalDegrees) / (Delta.Longitude.DecimalDegrees * Delta.Longitude.DecimalDegrees + Delta.Latitude.DecimalDegrees 
                * Delta.Latitude.DecimalDegrees);
			if(Ratio < 0)
				return position.DistanceTo(pStart);
			else if(Ratio > 1)
				return position.DistanceTo(pEnd);
			else
			{
				Position Destination = new Position(
                    new Latitude((1 - Ratio) * pStart.Latitude.DecimalDegrees + Ratio * pEnd.Latitude.DecimalDegrees),
					new Longitude((1 - Ratio) * pStart.Longitude.DecimalDegrees + Ratio * pEnd.Longitude.DecimalDegrees));
				return position.DistanceTo(Destination);
			}
		}




#if !PocketPC || Framework20
		#region IXmlSerializable Members

#if Framework20
        public static XmlQualifiedName GetXmlSchema(XmlSchemaSet xs)
        {
            // Load it from a string
            xs.Add(Xml.SchemaSet);

            // And return the type
            return new XmlQualifiedName("Segment", Xml.Namespace);
        }
#endif


        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            ReadXml(reader);
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            WriteXml(writer);
        }

        /// <summary>
        /// Populates the current instance by reading XML from the specified reader.
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            XmlSerializer PositionSerializer = new XmlSerializer(typeof(Position));

            // Now read in the remaining values
            while (!reader.EOF)
            {
                // Keep reading until we reach an element
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (reader.Name)
                        {
                            case "Start":
                                reader.Read();
                                pStart = (Position)PositionSerializer.Deserialize(reader);
                                break;
                            case "End":
                                reader.Read();
                                pEnd = (Position)PositionSerializer.Deserialize(reader);
                                break;
                            default:
                                // Read the next item
                                reader.Read();
                                break;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        // Is this the </SEGMENT> end tag?
                        if (String.Compare(reader.Name, "Segment", true, CultureInfo.InvariantCulture) == 0)
                            // Yes.  We're done
                            return;
                        else
                            // No.  Keep reading
                            reader.Read();
                        break;
                    default:
                        // Read the next item
                        reader.Read();
                        break;
                }
            }
        }

        /// <summary>
        /// Writes the values of the current instance to the specified XML writer.
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            XmlSerializer PositionSerializer = new XmlSerializer(typeof(Position));

            writer.WriteStartElement("Start");
            PositionSerializer.Serialize(writer, pStart);
            writer.WriteEndElement();

            writer.WriteStartElement("End");
            PositionSerializer.Serialize(writer, pEnd);
            writer.WriteEndElement();
        }

        #endregion
#endif

#if !PocketPC || DesignTime
        #region ISerializable Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]  
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Start", pStart);
            info.AddValue("End", pEnd);
        }
        #endregion
#endif

        #region IFormattable Members

        public override string ToString()
        {
            return ToString("G", CultureInfo.CurrentCulture);
        }

        public string ToString(string format, CultureInfo culture)
        {
            return pStart.ToString(format, culture) + culture.TextInfo.ListSeparator + pEnd.ToString(format, culture);
        }

        string IFormattable.ToString(string format, IFormatProvider formatProvider)
        {
            return ToString(format, (CultureInfo)formatProvider);
        }

        #endregion
    }
}
